热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

本文|被覆_一个栈溢出C++示例

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一个栈溢出C++示例相关的知识,希望对你有一定的参考价值。本文主要演示、分析、测试函数内变量越界的问题,即

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一个栈溢出C++示例相关的知识,希望对你有一定的参考价值。


本文主要演示、分析、测试函数内变量越界的问题,即栈溢出。



问题提出

很久前在测试某个工程时,发现一直能工作的模块出现了段错误。由于代码复杂,又有其它事耽搁,直到最近才集中时间调试。那个模块是循环vector,在其中计算数据再组装成字符串,最终将所有结果写到文件中。经测试发现,是在某次循环时出错。抽象化后的代码示例如下:

len = vPath.length();
for (int i = 0; i // 处理逻辑
// 处理逻辑
// 若干次调用sprintf()组装字符串

// 循环第N次出错

开始以为是处理逻辑部分出错,后发现在某次调用sprintf之后,i的值变得十分大。超过了vector容量,因此造成段错误。

后来确认,是sprintf组装的缓冲区越界,i的值被覆盖了。因为当时加代码片段时,没有留意缓冲区大小问题,加大容量即可解决问题。


工程代码

先给出变量的设计,如下:

int ret = 0;
int type = 10;
int id = 0;
char buffer[32] = 0;

因为本文就是模拟栈溢出情况,而栈是向下(低地址)增长的,为了让缓冲区buffer溢出覆盖其它变量,因此将其放到最后定义,其大小为32(十六进制为0x20),这样一旦溢出,就会越界波及干扰到rettypeid这几个变量,它们均为int类型,指针大小为4字节。

完整代码如下:

#include
#include
#include

// 打印buffer的内存数据
void dump(const char *buffer, int len)
int i, j, n;
int line = 16;
char c;
unsigned char* buf = (unsigned char *)buffer; // 必须是unsigned char类型

n = len / line;
if (len % line)
n++;

for (i=0; i
//printf("0x%08x: ", (unsigned int)(buf+i*line)); // linux ok
printf("0x%8p: ", buf+i*line); // windows ok

for (j=0; j
if ((i*line+j) printf("%02x ", buf[i*line+j]);
else
printf(" ");


printf(" ");
for (j=0; j
if ((i*line+j)
c = buf[i*line+j];
printf("%c", c >&#61; &#39; &#39; && c <&#39;~&#39; ? c : &#39;.&#39;);

else
printf(" ");


printf("\\n");


int main(void)
int ret &#61; 0;
int type &#61; 10;
int id &#61; 0;
char buffer[32] &#61; 0;

//dump(buffer, 48);

for (int i &#61; 0; i <16; i&#43;&#43;)

printf("---- type: %d id: %d i:%d \\n", type, id, i);
ret &#43;&#61; sprintf(buffer&#43;ret, "helloworld type: %d id: %d ", type, id);
printf("write total len: %d(0x%x)\\n", ret, ret);
printf("&#43;&#43;&#43;&#43; type: %d(0x%x) id: %d(0x%x) i: %d(0x%x)\\n", type, type, id, id, i, i);
type &#43;&#43;;
id &#43;&#43;;

printf("ptr ret: %p type: %p id: %p\\n", &ret, &type, &id);

dump((char*)(buffer), 60);
return 0;

代码比较简单&#xff0c;循环组装字符串再保存到buffer中&#xff0c;注意&#xff0c;buffer的内容是累加的——这正是溢出的根本问题。为了方便观察&#xff0c;同时打印其它变量的值及地址。本文在 x86 平台测试&#xff0c;其为小端模式&#xff0c;因为打印的值需要倒着看。


测试

一次测试结果如下&#xff1a;

---- type: 10 id: 0 i:0
write total len: 26(0x1a)
&#43;&#43;&#43;&#43; type: 10(0xa) id: 0(0x0) i: 0(0x0)
---- type: 11 id: 1 i:1
write total len: 824195711(0x31203a7f)
&#43;&#43;&#43;&#43; type: 1887007776(0x70797420) id: 1684828783(0x646c726f) i: 1684611121(0x64692031)
ptr ret: 000000000022FE48 type: 000000000022FE44 id: 000000000022FE40
0x000000000022FE20: 68 65 6c 6c 6f 77 6f 72 6c 64 20 74 79 70 65 3a helloworld type:
0x000000000022FE30: 20 31 30 20 69 64 3a 20 30 20 68 65 6c 6c 6f 77 10 id: 0 hellow
0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ.: 12 id
0x000000000022FE50: 3a 20 31 20 00 00 00 00 c7 13 40 00 : 1 ......&#64;.

下面分析执行情况&#xff1a;


  • 循环开始&#xff0c;第一次一切正常。
  • 循环到第二次时&#xff0c;缓冲区溢出&#xff0c;retid变量的值十分大。i亦然&#xff0c;故循环退出&#xff0c;由于代码没有用i作索引&#xff0c;因为没有段错误。

溢出数值分析如下&#xff1a;


  • buffer地址为0x000000000022FE20&#xff0c;变量id靠近buffer&#xff0c;其地址buffer后的32字节偏移处&#xff0c;为000000000022FE40&#xff0c;接着是type&#xff0c;偏移4字节&#xff0c;地址为000000000022FE44&#xff0c;ret地址是000000000022FE48

  • id溢出后的值是1684828783(0x646c726f)&#xff0c;观察对应打印的二进制&#xff1a;0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ.: 12 id&#xff1a;。如下&#xff1a;

    0x000000000022FE40: 70 72 6c 64 ... prld

    应该是hello world最后4字节orld&#xff0c;但有乱码。

  • type溢出后的值是1887007776(0x70797420)&#xff0c;观察对应打印的二进制&#xff1a;0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld !typ.: 12 id&#xff1a;。

  • ret溢出后的值是824195711(0x31203a7f)&#xff0c;观察对应打印的二进制&#xff1a;0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ .: 12 id&#xff1a;。

综合测试分析情况&#xff0c;那些变量溢出后的数值&#xff0c;基本上就是写到buffer越界后的数据。


扩展知识

栈、堆是不同的概念——因此前面提及的仅是栈&#xff0c;理论上栈、堆都有溢出的可能。

栈溢出一般有2种可能&#xff1a;无限递归&#xff0c;变量或数组定义很大。以linux系统为例&#xff0c;栈的大小为8MiB。可用ulimit -a查看&#xff1a;

$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 253387
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

其中stack size为8192&#xff0c;即8MiB。另外也能从中知晓文件句柄数量最大为1024(open files字段)。

文中的“溢出”是指写到buffer数组的内容超过其容量&#xff0c;占用了其它变量的空间。至于其它的溢出情况&#xff0c;就不再深究了。


小结

本文出现的问题&#xff0c;主要原因是缓冲区容量不足引起溢出的。幸好不是生产环境的&#xff0c;否则又得急忙救火了&#xff0c;但也给自己提了个醒。编码一定要注意细节&#xff0c;内存的操作&#xff0c;数组的索引&#xff0c;指针的判断&#xff0c;等&#xff0c;都要谨慎。所谓“小心驶得万年船”&#xff0c;作为一名编码工具人&#xff0c;对代码要常怀敬畏之心。


推荐阅读
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 第七课主要内容:多进程多线程FIFO,LIFO,优先队列线程局部变量进程与线程的选择线程池异步IO概念及twisted案例股票数据抓取 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 流数据流和IO流的使用及应用
    本文介绍了流数据流和IO流的基本概念和用法,包括输入流、输出流、字节流、字符流、缓冲区等。同时还介绍了异常处理和常用的流类,如FileReader、FileWriter、FileInputStream、FileOutputStream、OutputStreamWriter、InputStreamReader、BufferedReader、BufferedWriter等。此外,还介绍了系统流和标准流的使用。 ... [详细]
  • 本文介绍了使用C++Builder实现获取USB优盘序列号的方法,包括相关的代码和说明。通过该方法,可以获取指定盘符的USB优盘序列号,并将其存放在缓冲中。该方法可以在Windows系统中有效地获取USB优盘序列号,并且适用于C++Builder开发环境。 ... [详细]
author-avatar
辽河儿女
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有